﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Green;

namespace Noris.Tools.TraceVisualiser.Green.Client
{
    #region TraceReader : reader pro soubory typu Klientský trace Noris
    /// <summary>
    /// TraceReader : reader pro soubory typu Klientský trace Noris
    /// </summary>
    public class TraceReader : ITraceReader, ITraceReaderWithAutoTimeShift
    {
		#region IPlugin Members
		/// <summary>
		/// Příznak, že Plugin je aktivní ve standardním běhu (tj. při běžném užiavtelském spuštění).
		/// Plně funkční pluginy tedy vrací true.
		/// Pluginy, které jsou ve vývoji, vrací false, takové jsou zobrazeny / spouštěny pouze v aplikaci, která běží v režimu Debug.
		/// </summary>
		bool IPlugin.Active { get { return true; } }
		#endregion
		#region ITraceReader, IDisposable Members
        /// <summary>
        /// Zde reader vrací výraz, který se použije v dialogu při výběru vstupních trace soubor.
        /// Různé readery mají různé vstupní soubory s různou příponou, a my nechceme žádný reader opomenout.
        /// Reader může umět zpracovávat více různých koncovek, proto se používá string[], každá položka obsahuje jeden filtr.
        /// Každý jednotlivý filtr je ve tvaru: "Textový popisek|filtr", například: "Aplikační trace Green (*.csv)|*csv".
        /// </summary>
        string[] ITraceReader.FileOpenFilters
        {
            get
            {
                return new string[]
                {
                    "Helios Green client trace files (*.csv)|*.csv"
                };
            }
        }
        /// <summary>
        /// Metoda má za úkol podívat se na název souboru,
        /// a vyjádřit se k tomu, zda se bude chtít podívat i dovnitř souboru (tím, že nastaví args.CheckContent = true).
        /// Metoda by měla (pokud se tento TraceReader dokáže s názvem souboru vypořádat) rozebrat název souboru na jméno serveru, a oddělit i pořadové číslo z názvu.
        /// Metoda absolutně nemá za úkol otevírat soubor, pracuje jen s názvem.
        /// Pokud se metoda bude chtít podívat do souboru, nastaví args.CheckContent = true, a za chvíli se vyvolá další metoda: this.CheckContent().
        /// </summary>
        /// <param name="args">Data o souboru (název a výsledky této metody)</param>
        /// <returns></returns>
        void ITraceReader.CheckFileName(TraceReaderCheckNameArgs args)
        {
            // Nechci se dívat dovnitř (možná časem změním názor):
            args.CheckContent = false;

            // Přípona má být ".csv"
            System.IO.FileInfo fileInfo = args.FileInfo;
            if (!String.Equals(fileInfo.Extension, ".csv", StringComparison.OrdinalIgnoreCase))
                return;

            // Jméno má začínat "Trace":
            string name = System.IO.Path.GetFileNameWithoutExtension(args.FileInfo.Name);
            if (!name.StartsWith("trace", StringComparison.OrdinalIgnoreCase))
                return;

            // ..., a má mít určitou strukturu:
            string fileRoot, nameDate, fileNumber;
            if (!ParseFileName(args.FileInfo.Name, out fileRoot, out nameDate, out fileNumber))
                return;
            if (fileNumber.Length == 0)
                return;

            DateTime date = DetectNameDate(nameDate);
            if (date == DateTime.MinValue)
                return;

            // Vypadá to nadějně, určíme název serveru (adresář, základ, číslo dne):
            args.ServerName = NorisClientConstants.ServerName + "_" + System.IO.Path.Combine(fileInfo.Directory.FullName, fileRoot) + "_" + date.ToString("yyyyMMdd");

            // Do argumentu vložím datum souboru odvozené z jeho záhlaví:
            args.TraceDate = date;

            // Šlo by to, podíváme se dovnitř:
            args.CheckContent = true;
        }
        /// <summary>
        /// Metoda je volaná tehdy, když tento reader se přihlásil ke čtení záhlaví souboru (v metodě CheckFileName, nastavil args.CheckContent = true).
        /// Tato metoda nemá za úkol číst data, ale podívat se dovnitř, a usoudit, zda my chceme tento soubor zpracovávat.
        /// Soubor je otevřen, a je k němu přístup přes argument
        /// </summary>
        /// <param name="args"></param>
        void ITraceReader.CheckContent(TraceReaderCheckContentArgs args)
        {
            // Nechci soubor zpracovávat (možná časem změním názor):
            args.Priority = 0M;

			// Ověřím titulkový řádek:
			// Zatím umím jen tento řádek:
			string expected = "Date;Time;Offset;GCMemory;Stack;Assembly;Level;BSE;BEDepth;BEOffset;ThreadId;ActiveWindowHandle;ActivePageId;Type;Method;Keyword;User0;User1;User2";
			string title = args.ReadFirstLine();
			if (title.Length != expected.Length) return;
            if (String.Compare(title, expected, StringComparison.CurrentCultureIgnoreCase) != 0) return;

            // Najdu začátek a konec dat v souboru:
            TimeRange contentTime = TimeRange.Empty;
            string line = args.FileStreamReader.ReadLine();
            if (line != null && line.Length >= 23)
                contentTime.Begin = MainInfo.ConvertDateTime(line);

            // Najdu poslední řádek, který obsahuje alespoň 16 prvků oddělených středníkem:
            line = args.ReadLastLine(";", 16);
            if (line != null && line.Length >= 23)
                contentTime.End = MainInfo.ConvertDateTime(line);
            else
                contentTime.End = contentTime.Begin;

            // Pokud jsem našel nějaká data, pak bych takový soubor mohl zpracovat:
            if (contentTime.Begin > DateTime.MinValue)
            {
                args.Priority = 1M;
                args.ContentTime = contentTime;
				args.TraceType = NorisClientConstants.TraceType;
                args.ServerTimePriority = 3;                             // Tento server má nejméně nejdůvěryhodnější čas
                this._PrepareContentElementTexts(args);
                this._PrepareContentTreePaths(args);
            }
        }
        /// <summary>
        /// Sestaví texty, které se budou vypisovat v elementu a v tooltipu
        /// </summary>
        /// <param name="args"></param>
        private void _PrepareContentElementTexts(TraceReaderCheckContentArgs args)
        {
            string pathDir1 = args.FileInfo.Directory.Name;         // Jméno prvního up adresáře
            string fileDir1 = Path.Combine(pathDir1, args.FileInfo.Name);  // jméno s prvním adresářem a s příponou
            string fileName = args.FileInfo.FullName;
            string fileRoot, nameDate, fileNumber;

            // Texty do elementu:
            if (ParseFileName(fileName, out fileRoot, out nameDate, out fileNumber))
                args.ElementTexts = new string[] 
                {
                    fileNumber,                                     // číslo
                    nameDate + "_" + fileNumber,                    // datum_číslo
                    Path.GetFileNameWithoutExtension(fileName),     // jméno bez adresáře a bez přípony
                    args.FileInfo.Name,                             // jméno bez adresáře s příponou
                    fileDir1,                                       // jméno s prvním adresářem a s příponou
                    fileName                                        // full filename
                };
            else
                args.ElementTexts = new string[] 
                {
                    Path.GetFileNameWithoutExtension(fileName),     // jméno bez adresáře a bez přípony
                    args.FileInfo.Name,                             // jméno bez adresáře s příponou
                    fileDir1,                                       // jméno s prvním adresářem a s příponou
                    fileName                                        // full filename
                };

            // Tooltip:
            args.ElementTooltip = "Vstupní soubor: " + fileDir1 + Environment.NewLine +
                "Obsahuje data za čas: " + args.ContentTime.StringShort;
        }
        /// <summary>
        /// Do argumentu připraví nody, které budeme později pravděpodobně používat:
        /// </summary>
        /// <param name="args"></param>
        private void _PrepareContentTreePaths(TraceReaderCheckContentArgs args)
        {
            // Plná cesta k nodu, absolutní (true), node = Klienti, graf = elementy se nenačítají, podřízené řádky se načítají dynamicky, třídí se podle textu:
            TreePath nodeClient = new TreePath(true);
            nodeClient.AddItem(CreateTreePathItemClients());

            // Připravené dva nody dáme do TreePaths:
            args.TreePaths = new TreePath[] { nodeClient };
        }
        /// <summary>
        /// Metoda je volaná pro zpracování celého obsahu dat.
        /// Veškerou podporu má v argumentu TraceReaderReadItemArgs args.
        /// Metoda si může libovolně zpracovat vstupující soubor 
        /// a vytvářet z něj data ve formě jednotlivých záznamů typu ITraceItem.
        /// Tyto záznamy předává ke zpracování do datového systému pomocí volání metody argumentu ProcessItem(ITraceItem item).
        /// Metoda takto může vynechat některé věty (nedávat je ke zpracování), anebo některé věty zpracovat více způsoby
        /// (například z jedné věty trace vytvoří dvě položky: jednu ktertá představuje aktivitu uživatele, a druhou která popisuje stav paměti).
        /// Tím, že zpracování probíhá v jedné metodě, si reader může udržovat kontinuální povědomí o načítaných datech, 
        /// a například o stavu paměti posílat položky při změně hodnoty (není třeba posílat 1000 x jednu hodnotu stavu paměti).
        /// </summary>
        /// <param name="args"></param>
        void ITraceReader.ProcessContent(TraceReaderProcessContentArgs args)
        {
            this._ProcessContent(args);
        }
        /// <summary>
        /// Tato metoda má za úkol vytvořit a vrátit logickou událost na základě fyzických dat načtených z trace
        /// (před malinkou chvilkou je objekt osobně načetl a vrátil v metodě ReadItem()).
        /// Pokud se vrátí null, pak tato položka trace nemá být zakládána do evidence.
        /// </summary>
        IEventItem ITraceReader.CreateIEventItem(IEventEdge data)
        {
            return _CreateIEventItem(data);
        }
        /// <summary>
        /// Metoda, která má za úkol vytvořit celou plně definovanou cestu k nodu.
        /// Tato metoda se volá zcela výjimečně - tehdy, když tento objekt předepisuje nějakou TargetPath, která dosud neexistuje.
        /// Pak si jádro vyžádá plnou cestu k nodu, včetně textů a definice všech nodů.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        void ITraceReader.CreateFullTreePath(TraceReaderCreatePathArgs args)
        {
            this.CreateFullTreePath(args);
        }
        void IDisposable.Dispose()
        {
        }
        #endregion
        #region Rozklad názvu souboru na prvky
        /// <summary>
        /// Ze jména souboru (adresář není třeba zadávat, ale nevadí) najde základ, datovou část a číslo souboru
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="fileRoot"></param>
        /// <param name="nameDate"></param>
        /// <param name="fileNumber"></param>
        internal static bool ParseFileName(string fileName, out string fileRoot, out string nameDate, out string fileNumber)
        {
            fileRoot = null;
            nameDate = null;
            fileNumber = null;
            string name = System.IO.Path.GetFileNameWithoutExtension(fileName);
            int x;

            // Na konci je číslo:
            x = name.LastIndexOf("_");
            if (x <= 1 || x >= name.Length) return false;
            fileNumber = name.Substring(x + 1);
            name = name.Substring(0, x);

            // Před číslem je datum:
            x = name.LastIndexOf("_");
            if (x <= 1 || x >= name.Length) return false;
            nameDate = name.Substring(x + 1);
            fileRoot = name.Substring(0, x);

            return true;
        }
        /// <summary>
        /// Z textu s datem v názvu souboru (text ve formě "YYMMDD") určí a vrátí datum trace.
        /// Pokud nejde určit, vrací MinValue.
        /// </summary>
        /// <param name="nameDate"></param>
        /// <returns></returns>
        internal static DateTime DetectNameDate(string nameDate)
        {
            DateTime result = DateTime.MinValue;
            if (!String.IsNullOrEmpty(nameDate) && nameDate.Length == 6)
            {
                int year, month, day;
                if (Int32.TryParse(nameDate.Substring(0, 2), out year) &&
                    Int32.TryParse(nameDate.Substring(2, 2), out month) &&
                    Int32.TryParse(nameDate.Substring(4, 2), out day))
                {
                    if (month > 0 && month <= 12 &&
                        day > 0 && day <= 31)
                    {
                        try
                        {
                            result = new DateTime(2000 + year, month, day);
                        }
                        catch
                        {
                            result = DateTime.MinValue;
                        }
                    }
                }
            }
            return result;
        }
        #endregion
		#region Fyzické načítání obsahu souboru a zpracování na položky ITraceItem
		/// <summary>
		/// Zpracuje data do jednotlivých položek
		/// </summary>
		/// <param name="args"></param>
		private void _ProcessContent(TraceReaderProcessContentArgs args)
		{
			TraceServerPersistData persistData = TraceServerPersistData.GetFrom(args.Server.Tag);
			try
			{
				this.StringHeap = args.Main.StringHeap;
				this.TitleLine = args.FileStreamReader.ReadLine();
				this.LineNumber = 0;

                // Určíme název procesu a jeho Int32 pointer:
                string processKey = args.FileInfo.Directory.Name + "_" + args.Trace.TraceDate.ToString("yyyyMMdd");
                persistData.CurrentTraceProcessKeyInt32 = this.StringHeap.GetInt32(processKey);

				while (!args.FileStreamReader.EndOfStream)
				{
					string lineText = args.FileStreamReader.ReadLine();
					this.LineNumber++;

					// Vytvořit a zpracovat položku (jedna položka může obsahovat mnoho různých cílových nodů = řádků, kde bude zobrazena):
					TraceItem traceItem = TraceItem.CreateItem(args.Main.StringHeap, (this.LineNumber + 1), lineText, ref persistData);
					if (traceItem != null)
					{
						args.ProcessItem(traceItem);
					}
				}
			}
			finally
			{
				args.Server.Tag = persistData;
				this.StringHeap = args.Main.StringHeap;
			}
		}
		/// <summary>
		/// Reference na zásobník textů
		/// </summary>
		protected StringHeap StringHeap { get; set; }
		protected string TitleLine { get; set; }
		protected int LineNumber { get; set; }
		#endregion
		#region Podpora pro tvorbu nodů grafu (řádky: klíče, názvy, definice grafiky a chování řádku)
		/// <summary>
		/// Sestavení plné cesty k nodu na základě jejího stringu
		/// </summary>
		/// <param name="args"></param>
		protected void CreateFullTreePath(TraceReaderCreatePathArgs args)
		{
			// Připravím si polotovar (tam se mi rozeberou jednotlivé složky cesty):
			TreePath input = new TreePath(args.Path);
			List<TreePathItem> items = input.Items;

			// Připravím si výsledek:
			TreePath result = new TreePath(input.AbsolutePath);
			string status = (input.AbsolutePath ? "root" : "node");           // Jakou část cesty budu nyní zpracovávat? Dynamicky se posouvá podle toho, jak se posouvám na konkrétní cestě
			foreach (TreePathItem item in items)
			{
				switch (status)
				{
					case "root":
						// Řeším node v root úrovni:
						// Podle klíče položky určím, co v ní je, a jakého typu bude příští položka:
						switch (item.Key)
						{
							case NorisClientConstants.NodeClient:
								result.AddItem(TraceReader.CreateTreePathItemClients());
								status = "client";               // V příští úrovni cesty (další TreePathItem item in items) očekávám konkrétního klienta
								break;
						}
						break;
					case "client":
						// V této úrovni bude login a jméno uživatele:
						result.AddItem(TraceReader.CreateTreePathItemCurrentClient(args));
						status = "detail";                     // V příští úrovni cesty budou detailní položky jednotlivého uživatele
						break;
					default:
						result.AddItem(TraceReader.CreateTreePathItemDefault(item.Key));
						break;
				}
			}
			args.TreePath = result;
		}
		/// <summary>
		/// Vrátí definici pro položku grafu s textem "Klienti", která se zobrazuje v root úrovni grafu.
		/// </summary>
		/// <returns></returns>
		internal static TreePathItem CreateTreePathItemClients()
		{
			return new TreePathItem(NorisClientConstants.NodeClient, "Klienti",
				GraphRowDeclarationCls.StandardMultiTask(14, 0.1M, RowProperties.DynamicSubNodeLoading | RowProperties.ChildSortedText));
		}
		/// <summary>
		/// Vrátí definici pro položku grafu s textem "Jeden Klient", která se zobrazuje jako podřízená položka k root nodu Klienti.
		/// </summary>
		/// <returns></returns>
        private static TreePathItem CreateTreePathItemCurrentClient(TraceReaderCreatePathArgs args)
		{
			string oneDir = args.Server.CurrentTraceInfo.FileInfo.Directory.Name;
			return new TreePathItem(oneDir, oneDir,
				GraphRowDeclarationCls.StandardMultiTask(14, 0.1M, RowProperties.DynamicElementsLoading));
		}
		/// <summary>
		/// Vrátí definici pro položku grafu defaultní, bez podrobnější znalost věci
		/// </summary>
		/// <returns></returns>
		internal static TreePathItem CreateTreePathItemDefault(string key)
		{
			string name = key;
			return new TreePathItem(key, name,
				GraphRowDeclarationCls.StandardMultiTask(14, 0.1M, RowProperties.PreLoadingElements | RowProperties.DynamicElementsLoading | RowProperties.DynamicSubNodeLoading));
		}
		#endregion
		#region Vytvoření logické události IEventItem na základě dat z trace IEventEdge
		/// <summary>
		/// Tato metoda má za úkol vytvořit a vrátit logickou událost na základě fyzických dat načtených z trace
		/// (před malinkou chvilkou je objekt osobně načetl a vrátil v metodě ReadItem()).
		/// Pokud se vrátí null, pak tato položka trace nemá být zakládána do evidence.
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		private IEventItem _CreateIEventItem(IEventEdge data)
		{
			if (data == null)
				return null;

			TraceItem item = data as TraceItem;
			if (item == null)
				Msg.Error("Chyba: pro vytvoření objektu IEventItem byl do readeru NorisASrvTraceReader předán nesprávný objekt.\r\nOčekávaný typ: {0}, chybný typ: {1}", typeof(TraceItem).Name, data.GetType().Name);

			EventItem thing = EventItem.CreateEventItem(this.StringHeap, item);

			return thing;
		}
		#endregion
        #region ITraceReaderWithAutoTimeShift : detekce Timeshiftu pro jeden trace soubor tohoto typu (Klientský trace)
        /// <summary>
        /// Metoda určí TimeShift pro svoje události oproti událostem jiným.
        /// Metoda vrátí true, pokud daný soubor je již zpracován a nebude třeba se k němu vracet.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        bool ITraceReaderWithAutoTimeShift.DetectTimeShift(TraceReaderDetectTimeShift args)
        {
            Dictionary<int, double> shiftData = new Dictionary<int, double>();

            // Určím časové offsety pro dostupné události:
            foreach (var process in args.Server.Processes)
            {
                foreach (EventInfo evnt in process.Events)
                {
                    EventItem eventItem = evnt.EventItem as EventItem;
                    if (eventItem != null && eventItem.IsEventForTimeShift)
                        this.DetectTimeShiftOne(args, eventItem, shiftData);
                }
            }

            // Data z shiftData propíšu do serveru:
            if (shiftData.Count > 0)
            {
                foreach (var shiftItem in shiftData)
                    args.Server.SetVisualTime(shiftItem.Key, shiftItem.Value, false);
                args.Server.SaveToDocument();
            }

            return true;
        }
        /// <summary>
        /// Zajistí detekci timeshiftu pro jednu událost z klienta
        /// </summary>
        /// <param name="args"></param>
        /// <param name="clientEvent"></param>
        /// <param name="shiftData"></param>
        protected void DetectTimeShiftOne(TraceReaderDetectTimeShift args, EventItem clientEvent, Dictionary<int, double> shiftData)
        {
            // Pokud náš "server" (=počítač klienta) již obsahuje informaci pro čas této události (v aktuální granularitě), nebudu to řešit:
            if (args.Trace.Server.ContainVisualOffsetForTime(clientEvent.Time.End))
                return;

            // Najdu procesy ze všech okolních serverů (=aplikační servery) pro daný SessionID (nejspíš to bude jeden proces):
            string sessionId = "I" + clientEvent.SessionID;
            IEnumerable<ProcessInfo> processes = args.Main.FindProcesses(p => String.Equals(p.Key, sessionId, StringComparison.InvariantCultureIgnoreCase));
            foreach (ProcessInfo process in processes)
            {   // Mám proces, který odpovídá zadanému SessionID. Najdu v něm událost (na APL serveru), která odpovídá dané události eventItem (na KLIENTU):
                EventInfo serverEvent = process.Events.FirstOrDefault(ei => this.DetectTimeShiftIsSync(args, clientEvent, ei));
                if (serverEvent != null)
                {
                    this.DetectTimeShiftOneSet(clientEvent.Time, serverEvent.Time, shiftData);
                    break;
                }
            }
        }
        /// <summary>
        /// Určí, zda události na klientu a na serveru mají být synchronní
        /// </summary>
        /// <param name="args"></param>
        /// <param name="clientEvent"></param>
        /// <param name="scanEvent"></param>
        /// <returns></returns>
        protected bool DetectTimeShiftIsSync(TraceReaderDetectTimeShift args, EventItem clientEvent, EventInfo scanEvent)
        {
            Green.AplServer.EventItem serverEvent = scanEvent.EventItem as Green.AplServer.EventItem;
            if (serverEvent == null) return false;
            if (!serverEvent.IsEventForTimeShift) return false;
            if (serverEvent.Time.Begin.Year < 2000) return false;                // Nemáme Begin
            // Našli jsme událost na APL serveru, která vypadá jako časová kotva (obecně).
            // Tedy má shodný SessionID a lze podle ní synchronizovat časy.
            // Pokud nemá shodné číslo ActionID, pak událost ignoruji.
            if (serverEvent.ActionID != clientEvent.ActionID) return false;
            return true;
        }
        /// <summary>
        /// Zajistí nápočet časového posunu za jednu nalezenou synchronní událost do mezipaměti (střádám nejmenší offset)
        /// </summary>
        /// <param name="clientTime"></param>
        /// <param name="serverTime"></param>
        /// <param name="shiftData"></param>
        protected void DetectTimeShiftOneSet(TimeRange clientTime, TimeRange serverTime, Dictionary<int, double> shiftData)
        {
            int key = TraceServerTimeOffset.TimeToKey(clientTime.End);                          // Vrátí klíč času. Zde se řeší granularita času = po jakých krocích ukládám časový offset.
            double offset = ((TimeSpan)(serverTime.Begin - clientTime.End)).TotalSeconds;       // Vzdálenost od konce události z klienta do začátku na serveru, v sekundách (včetně milisekund)
            double previous;
            if (shiftData.TryGetValue(key, out previous))
            {
                if (offset < previous)
                    shiftData[key] = offset;
            }
            else
            {
                shiftData.Add(key, offset);
            }
        }
        #endregion
    }
	/// <summary>
	/// Instance této třídy persistuje v ServerInfo.Tag mezi jednotlivými soubory Trace
	/// (tedy mezi jednotlivými voláními metody this._ProcessContent).
	/// Tím je umožněno předávat si kontinuální data do načítání dalšího souboru.
	/// </summary>
	internal class TraceServerPersistData
	{
		internal static TraceServerPersistData GetFrom(object tag)
		{
			TraceServerPersistData data = null;
			if (tag != null && tag is TraceServerPersistData)
				data = tag as TraceServerPersistData;
			if (data == null)
				data = new TraceServerPersistData();
			return data;
		}
		/// <summary>
		/// Stav GCMemory po poslední změně, i kdyby to bylo v minulém souboru
		/// </summary>
		internal int LastGcMemoryChangeValue { get; set; }
		/// <summary>
		/// Datum poslední změny hodnoty GCMemory, i kdyby to bylo v minulém souboru
		/// </summary>
		internal DateTime LastGcMemoryChangeDate { get; set; }
        /// <summary>
        /// Pointer do StringHeap pro název aktuálního procesu = odpovídá adresáři trace souboru a dni trace (ProcessKey je typicky "TraceDir_20111231").
        /// Určuje se při začátku zpracování obsahu každého souboru, opisuje se do každé události TraceItem a následně do EventInfo)
        /// </summary>
        internal Int32 CurrentTraceProcessKeyInt32 { get; set; }
	}
	#endregion
	#region TraceItem : fyzická data jedné položky (opis položky a ukazatel jejího cíle), načtené ze souboru Aplikační trace Noris. Zde se určují cílové Node pro událost.
	/// <summary>
	/// TraceItem : fyzická data jedné položky, načtené ze souboru Aplikační trace Noris
	/// </summary>
	public class TraceItem : IEventEdge
	{
		#region Konstrukce, parsování
		private TraceItem(StringHeap stringHeap)
		{
			this.StringHeap = stringHeap;
            this.FilterKeyword = "*";
		}
		private StringHeap StringHeap;
		/// <summary>
		/// Vytvoří a vrátí data.
		/// Pokud data nejsou korektně načtena (nesprávný řádek), vrátí null.
		/// </summary>
		/// <param name="stringHeap"></param>
		/// <param name="lineNumber"></param>
		/// <param name="lineText"></param>
		/// <param name="persistData"></param>
		/// <returns></returns>
		internal static TraceItem CreateItem(StringHeap stringHeap, int lineNumber, string lineText, ref TraceServerPersistData persistData)
		{
			TraceItem item = new TraceItem(stringHeap);
			item.LineNumber = lineNumber;
            item._ProcessKeyInt32 = persistData.CurrentTraceProcessKeyInt32;    // Do položky ukládám (pointer na) klíč procesu = "adresář_datum"
			item.ParseLine(lineText);
			if (!item._LineIsParsed) return null;                               // odbočka při nekorektním tvaru řádku
			item.DetectFilter();
			item.DetectMemoryChange(ref persistData);
			//item.DetectTargets();
			return item;
		}
		/// <summary>
		/// Detekuje změnu hodnoty paměti. Při změně si nastaví příznak, a následně připraví Target i do řádku Memory.
		/// </summary>
		/// <param name="persistData"></param>
		internal void DetectMemoryChange(ref TraceServerPersistData persistData)
		{
			this.MemoryChanged = (this.GCMemory != persistData.LastGcMemoryChangeValue);
			if (this.MemoryChanged)
			{
				persistData.LastGcMemoryChangeValue = this.GCMemory;
				persistData.LastGcMemoryChangeDate = this.Time;
			}
		}
		/// <summary>
		/// Parsování textu řádku do proměnných.
		/// </summary>
		private void ParseLine(string lineText)
		{
			if (String.IsNullOrEmpty(lineText)) return;

            // Struktura řádku:
            //  0    1    2      3        4     5        6     7   8       9        10       11                 12           13   14     15      16    17    18
            // Date;Time;Offset;GCMemory;Stack;Assembly;Level;BSE;BEDepth;BEOffset;ThreadId;ActiveWindowHandle;ActivePageId;Type;Method;Keyword;User0;User1;User2
            // 2011/12/21;15:31:09.334;0;8006;;LCS.ClientControllers.dll;I;S;;;006-0001;-2147483648;-2147483648;ServerResources;_ParsingServerResource_Delay;common;00:00:00.5773161

			string[] items = lineText.Split(';');
			int length = items.Length;
			if (length < 16)
				return;

			// Parsování:
			this._Time = MainInfo.ConvertDateTime(items[0] + ";" + items[1]);
			Int32.TryParse(items[2], out this._Offset);
			Int32.TryParse(items[3], out this._GCMemory);
			this._Stack = this.StringHeap.GetInt32(items[4]);
			this._Assembly = this.StringHeap.GetInt32(items[5]);
			this._Level = GetIWEType(items[6]);
			this._BSE = GetBSEType(items[7]);
			if (items[8] == "" || items[8] == "0")
				this._BEDepth = 0;       // V trace se u systémových událostí objevuje BEDepth = "" (nebo "0"?), což tam znamená "nic". Ale pro nás "nic" = null.
			else
				this._BEDepth = this.StringHeap.GetInt32(items[8]);
			Int32.TryParse(items[9], out this._BEOffset);
            this._ThreadIdInt32 = this.StringHeap.GetInt32(items[10]);
            Int32.TryParse(items[11], out this._ActiveWindowHandle);
            Int32.TryParse(items[12], out this._ActivePageId);
            this._Type = this.StringHeap.GetInt32(items[13]);
            this._Method = this.StringHeap.GetInt32(items[14]);
            this._Keyword = this.StringHeap.GetInt32(items[15]);

			// UserData:
			if (length >= 17)
			{
				int userLength = length - 16;   // Počet položek UserNnn je = počet položek nad rámec základních 16

				this._UserLength = userLength;
				this._User = new Int32[userLength];
				for (int i = 0; i < userLength; i++)
					this._User[i] = this.StringHeap.GetInt32(items[16 + i]);
			}
			else
			{
				this._UserLength = 0;
				this._User = null;
			}

			// Dokončení:
			this._LineIsParsed = true;
		}
		/// <summary>
		/// Begin / End / System ?
		/// </summary>
		protected LineBSE GetBSEType(string bse)
		{
			switch (bse)
			{
				case "B": return LineBSE.Begin;
				case "S": return LineBSE.System;
				case "E": return LineBSE.End;
			}
			return LineBSE.None;
		}
		/// <summary>
		/// Information / Warning / Error ?
		/// </summary>
		protected LineIWE GetIWEType(string iwe)
		{
			switch (iwe)
			{
				case "I": return LineIWE.Information;
				case "W": return LineIWE.Warning;
				case "E": return LineIWE.Error;
                case "D": return LineIWE.Debug;
			}
			return LineIWE.None;
		}
		// Proměnné typované Int32 obsahují pointer na string do StringHeap:
		private DateTime _Time;
		private int _Offset;
		private int _GCMemory;
		private Int32 _Stack;
		private Int32 _Assembly;
		private LineIWE _Level;
		private LineBSE _BSE;
		private Int32 _BEDepth;
        private Int32 _ProcessKeyInt32;
		private int _BEOffset;
        private Int32 _ThreadIdInt32;
        private int _ActiveWindowHandle;
        private int _ActivePageId;
		private Int32 _Type;
		private Int32 _Method;
		private Int32 _Keyword;
		private int _UserLength;
		private Int32[] _User;

		private bool _LineIsParsed;
		#endregion
		#region IEventPointer, IDisposable Members
		/// <summary>
		/// Klíč procesu. Nemůže být prázdný (pak se event nikam nezařadí; ledaže bychom povolili jeden empty proces - ale zatím ne).
		/// </summary>
		string IEventPointer.ProcessKey { get { return "Client"; } }
		/// <summary>
		/// Klíč události. Může být empty, pak ale nebude možno do této události přidávat další data o události (IEventEdge), 
		/// protože je nebude možno spárovat.
		/// </summary>
		string IEventPointer.EventKey { get { return this.BEDepth; } }
		/// <summary>
		/// Pořadové číslo události v rámci trace souboru, 
		/// slouží k přesnému třídění pořadí událostí tehdy, když čas dvou sousedních událostí je zcela shodný.
		/// Ono za 1 milisekundu lze stihnout desítky věcí...
		/// </summary>
		int IEventPointer.LineNumber { get { return this.LineNumber; } }
		void IDisposable.Dispose()
		{
			this._Stack = 0;
			this._Assembly = 0;
			this._BEDepth = 0;
            this._ProcessKeyInt32 = 0;
            this._ActiveWindowHandle = 0;
            this._ActivePageId = 0;
			this._Type = 0;
			this._Method = 0;
			this._Keyword = 0;
			this._User = null;

			this.FilterKeyword = null;
		}
		#endregion
		#region Property řádku - načtené z fyzického podkladu
		public int LineNumber { get; private set; }
		public DateTime Time { get { return _Time; } }
		public int Offset { get { return _Offset; } }
		public int GCMemory { get { return _GCMemory; } }
		public string Stack { get { return this.StringHeap.GetString(_Stack); } }
		internal Int32 StackInt32 { get { return _Stack; } }
		public string Assembly { get { return this.StringHeap.GetString(_Assembly); } }
		internal Int32 AssemblyInt32 { get { return _Assembly; } }
		public LineIWE Level { get { return _Level; } }
		public LineBSE BSE { get { return _BSE; } }
		public string BEDepth { get { return this.StringHeap.GetString(_BEDepth); } }
		internal Int32 BEDepthInt32 { get { return _BEDepth; } }
		public string ProcessKey { get { return this.StringHeap.GetString(_ProcessKeyInt32); } }
		internal Int32 ProcessKeyInt32 { get { return _ProcessKeyInt32; } }
		public int BEOffset { get { return _BEOffset; } }
        public string ThreadId { get { return this.StringHeap.GetString(_ThreadIdInt32); } }
        public Int32 ThreadIdInt32 { get { return _ThreadIdInt32; } }
        public int ActiveWindowHandle { get { return _ActiveWindowHandle; } }
        public int ActivePageId { get { return _ActivePageId; } }
        public string Type { get { return this.StringHeap.GetString(_Type); } }
		internal Int32 TypeInt32 { get { return _Type; } }
		public string Method { get { return this.StringHeap.GetString(_Method); } }
		internal Int32 MethodInt32 { get { return _Method; } }
		public string Keyword { get { return this.StringHeap.GetString(_Keyword); } }
		internal Int32 KeywordInt32 { get { return _Keyword; } }
		public int UserLength { get { return _UserLength; } }
		public Int32[] UserDataInt32 { get { return _User; } }
		public string User(int index)
		{
			return this.StringHeap.GetString(_User[index]);
		}
		#endregion
		#region Další property, určující zatřídění řádku
		/// <summary>
		/// Příznak, že tato položka má jinou hodnotu paměti, než předešlá událost v řadě.
		/// </summary>
		public bool MemoryChanged { get; protected set; }
		/// <summary>
		/// Klíč skupiny událostí, které lze mezi sebou mergovat.
		/// </summary>
		public string MergeKey { get { return this.StringHeap.GetString(MergeKeyInt32); } }
		internal Int32 MergeKeyInt32 { get; set; }
		#endregion
		#region Podpora pro filtrování
		/// <summary>
		/// Klíčové slovo pro filtraci událostí.
		/// Uživatel může ve vlastnostech projektu určit, jaké události chce načítat (specifikuje je jako soupis stringových konstant).
		/// Každá událost pak musí uvádět svoje klíčové slovo, které je při načítání dat porovnáváno se zadaným filtrem, a pouze vyhovující události se přebírají do systému.
		/// Reader události nefiltruje, to zajišťuje datový systém.
		/// Názvy položek filtru jsou uvedeny na třídě TraceDocument jako konstanty začínající slovem FILTER_*.
		/// Speciální význam mají hodnoty: 
		/// a) hodnota null nebo empty: událost bude zobrazena jen v případě, kdy uživatel zaškrtne VŠE
		/// b) hodnota * bude zobrazena vždy, i když by uživatel nic nepovolil
		/// </summary>
		public string FilterKeyword { get; set; }
		/// <summary>
		/// Určí hodnotu FilterKeyword
		/// </summary>
		protected void DetectFilter()
		{
            // Z klienta budu zobrazovat všechno:
            this.FilterKeyword = "*";
            return;



			string keyword = this.Keyword.Trim().ToUpper();
			string method = this.Method.Trim().ToUpper();

			// Systémové události:
			if (this.BSE == LineBSE.System && keyword != "TRANS")
			{
				switch (this.Level)
				{
					case LineIWE.Error:
						this.FilterKeyword = TraceDocument.FILTER_SYSMSG_ERROR;
						break;
					case LineIWE.Warning:
						this.FilterKeyword = TraceDocument.FILTER_SYSMSG_WARNING;
						break;
					case LineIWE.Information:
						this.FilterKeyword = TraceDocument.FILTER_SYSMSG_INFO;
						break;
                    case LineIWE.Debug:
                        this.FilterKeyword = TraceDocument.FILTER_SYSMSG_DEBUG;
                        break;
                }
				return;
			}

			// Párové události + TRANS:
			switch (keyword)
			{
				// --- Keyword -------------- Method -------
				case "ATTEMPT":
					this.FilterKeyword = TraceDocument.FILTER_FORMAT;
					break;
				case "AFTER":             // ItemChanged
				case "BEFORE":
				case "ITEMCHANGEDBEFORE":
					this.FilterKeyword = TraceDocument.FILTER_INSTANCE_CHANGE;
					break;
				case "AUDITOR":           // Begin, End
					this.FilterKeyword = TraceDocument.FILTER_FORMAT;
					break;
				case "CACHE":
					this.FilterKeyword = TraceDocument.FILTER_CACHE;
					break;
				case "CLIENTERROR":
					this.FilterKeyword = TraceDocument.FILTER_FORMS;
					break;
				case "COWLEY":            // _GetCowley, _Run1_nonvisualPart, _Run2_progressbarPart, _Run3_rundonePart, _Run5_invokePart
					this.FilterKeyword = TraceDocument.FILTER_COWLEY;
					break;
				case "DATA":
				case "DATACONTAINER":     // _Retrieve, Invalidate, SetInvalidate, 
				case "DATAFORM":          // 
				case "DATASTORE":
				case "ERROR":
				case "EXTENSIONS":
				case "FORMAT":
				case "FORMDETAIL":
				case "GETINFO":
				case "GETTEMPLATE":
					this.FilterKeyword = TraceDocument.FILTER_FORMAT;
					break;
				case "INSTANCE":          // _InitDataStores, _InitDataStoresGetSelects, GetInstance, Init, NewEntry, RecountEntries, Reset, Retrieve, SetFolder, Update
					switch (method)
					{
						case "RESET":
						case "UPDATE":
						case "RETRIEVE":
							this.FilterKeyword = TraceDocument.FILTER_INSTANCE_RUR;
							break;
						default:
							this.FilterKeyword = TraceDocument.FILTER_INSTANCE_ALL;
							break;
					}
					break;
				case "LOGIN":
					this.FilterKeyword = "*";        // Zobrazí se vždy
					break;
				case "MENUACTION":
				case "ONDBLCLICK":
					this.FilterKeyword = TraceDocument.FILTER_FORMS;
					break;
				case "ONROWCHANGED":
					this.FilterKeyword = TraceDocument.FILTER_INSTANCE_CHANGE;
					break;
				case "OPENWINDOW":
					this.FilterKeyword = TraceDocument.FILTER_FORMS_OPEN;
					break;
				case "PERIODS":
				case "PROGRESS":
					this.FilterKeyword = TraceDocument.FILTER_FORMS;
					break;
				case "RECORDRULES":       // FlowDefaults, Reset, TestSave
					this.FilterKeyword = TraceDocument.FILTER_INSTANCE_IOM;
					break;
				case "RELATIONS":         // SetFolder, 
					this.FilterKeyword = TraceDocument.FILTER_INSTANCE_CHANGE;
					break;
				case "ROOT":
				case "SHOWERROR":
					this.FilterKeyword = TraceDocument.FILTER_FORMS;
					break;
				case "SQL":               // _CreateSummaryTable, _DoUpdate, _ExecuteNonQuery, _GetDynRelCountForAllFolders, _getvalue, _LoadMore, ExecuteScalar, Fill(1), GetDbDataReader
					this.FilterKeyword = TraceDocument.FILTER_SQL;
					break;
				case "STORAGE":
				case "STORES":
				case "SWT":
				case "USERSCOUNT":
					this.FilterKeyword = TraceDocument.FILTER_CACHE;
					break;
				case "TEMPLATE":
					this.FilterKeyword = TraceDocument.FILTER_FORMS;
					break;
				case "THREAD":            // ProcessRequest
					this.FilterKeyword = TraceDocument.FILTER_THREAD;
					break;
				case "TRANS":             // _CheckState, Begin, End(Begin), End(End)
					if (this.BSE == LineBSE.System && method == "_CHECKSTATE")
						this.FilterKeyword = "";        // TRANS typu S je _CheckState, generuje položku Info, a generuje jich moc moc moc: ty zobrazím jen když uživatel chce VŠE!
					else
						this.FilterKeyword = TraceDocument.FILTER_SQL;
					break;
				case "TUNNEL":
					this.FilterKeyword = TraceDocument.FILTER_THREAD;
					break;
				case "WARNING":
					this.FilterKeyword = TraceDocument.FILTER_SYSMSG_WARNING;
					break;
				default:
					this.FilterKeyword = "";         // Jen když uživatel zaškrtne VŠE
					break;
			}
		}
		#endregion
	}
	#endregion
	#region Konstanty NorisClient
	/// <summary>
	/// Konstanty používané v datech klientského trace
	/// </summary>
	public class NorisClientConstants
	{
		/// <summary>
		/// NorisWin32ClientTrace (druh dat v trace souboru)
		/// </summary>
        public const string TraceType = "NorisWin32ClientTrace";
        /// <summary>
        /// GreenWin32Client = název logického serveru v TraceData
        /// </summary>
        internal const string ServerName = "GreenWin32Client";
        /// <summary>
		/// 60_Client
		/// </summary>
		internal const string NodeClient = "60_Client";
		/// <summary>
		/// \60_Client
		/// </summary>
		internal const string RootNodeClient = "\\" + NodeClient;

		/// <summary>
		/// "kernel." : prefix ColorGroup pro všechny elementy jádra
		/// </summary>
		internal const string ColorGroupKernel = "kernel.";
		/// <summary>
		/// "instance." : prefix ColorGroup pro všechny elementy instance
		/// </summary>
		internal const string ColorGroupInstance = "instance.";
	}
	#endregion
}
